package dyyx;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.ServerSocket;
import java.net.Socket;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;

public class SimpleHttpServer2 {

    public static final String SEP = ",";
    public static final String EMPTY = "";
    public static final String BLANK = " ";
    public static final String COLON = ":";

    private static final String DEFAULT_CONTENT_TYPE = "text/html;charset=utf8";
    // Carriage-Return Line-Feed
    private static final String CRLF = "\r\n";
    // private static final String HEAD_SEP = ":";
    // private static final String BLANK = " ";
    private static final String STATUS_200 = "HTTP/1.1 200 OK";
    private static final String UTF8 = "UTF8";
    private static final int PORT = 8050;

    public static void main(String[] args) throws Exception {
        // -Dport=8888
        int port = getPort();
        start(port);
        System.out.println("GCTestMain simple http server start,port=" + port);
        System.out.println("GET request support only");

        // ServerThread 非后台线程 ， main 函数退出 ，jvm 不会退出
    }

    private static final int getPort() {
        int port = PORT;
        try {
            port = Integer.parseInt(System.getProperty("port"));
        } catch (Throwable e) {
            //
        }
        if (port <= 0) {
            port = PORT;
        }
        return port;
    }

    public static void start(int port) throws Exception {

        try {
            final ServerSocket serverSocket = new ServerSocket(port);
            // 设置超时 accept 会抛异常
            // java.net.SocketTimeoutException: Accept timed out
            // 3000 ms
            // serverSocket.setSoTimeout(3000);
            ServerThread serverThread = new ServerThread(serverSocket);
            serverThread.setName("serverThread-" + port);
            serverThread.start();
        } catch (Throwable e) {
            System.out.println("start error,port=" + port + "," + e);
            throw e;
        }
    }

    private static final class ServerThread extends Thread {
        final ServerSocket serverSocket;

        public ServerThread(ServerSocket serverSocket) {
            this.serverSocket = serverSocket;
        }

        public void run() {
            while (true) {
                try {
                    Socket socket = serverSocket.accept();
                    // 设置 超时时间 ，readLine 会超时
                    // java.net.SocketTimeoutException: Read timed out
                    // socket.setSoTimeout(3000);
                    socket.setTcpNoDelay(true);
                    System.out.println("socket=" + socket);
                    WorkThread workThread = new WorkThread(socket);
                    workThread.start();
                } catch (Throwable e) {
                    System.out.println(LocalDateTime.now() + " accept error," + e);
                }

            }
        }
        // run end
    }

    // ServerThread end
    private static List<String> readLines(BufferedReader reader) throws IOException {
        List<String> list = new ArrayList<>();
        while (true) {
            String line = reader.readLine();
            if (line == null || line.isEmpty()) {
                break;
            }
            list.add(line);
        }
        return list;
    }

    private static StringBuilder buildResponse(String body) throws UnsupportedEncodingException {
        if (body == null) {
            body = "";
        }
        StringBuilder sb = new StringBuilder(128);
        sb.append(STATUS_200);
        sb.append(CRLF);
        // Content-Type: text/html;charset=ISO-8859-1
        // Content-Length: 527
        sb.append("Content-Type: ");
        sb.append(DEFAULT_CONTENT_TYPE);
        sb.append(CRLF);

        byte[] bytes = body.getBytes(UTF8);

        sb.append("Content-Length: ");
        sb.append(bytes.length);
        sb.append(CRLF);
        //
        sb.append(CRLF);

        sb.append(body);
        return sb;
    }

    private static final class WorkThread extends Thread {
        final Socket socket;
        final InputStream inputStream;
        final OutputStream outputStream;
        final BufferedReader reader;
        final BufferedWriter writer;
        public static final AtomicLong count = new AtomicLong(0);

        public static final AtomicLong workThreadCount = new AtomicLong(0);

        public WorkThread(Socket socket) throws IOException {
            this.socket = socket;
            this.inputStream = socket.getInputStream();
            this.outputStream = socket.getOutputStream();
            this.reader = new BufferedReader(new InputStreamReader(inputStream, UTF8));
            this.writer = new BufferedWriter(new OutputStreamWriter(outputStream, UTF8));
            this.setName("workThread-" + workThreadCount.incrementAndGet());
        }

        public void run() {
            while (true) {
                try {
                    List<String> lines = readLines(reader);
                    if (lines == null || lines.isEmpty()) {
                        // 客户端已关闭 ，会读到空数据
                        // curl http://127.0.0.1:8070/hello
                        close(socket);
                        break;
                    }
                    long currentCount = count.incrementAndGet();
                    System.out.println(currentCount + "," + lines);

                    // GET /hello HTTP/1.1
                    String firstLine = lines.get(0);
                    List<String> values = split(firstLine, BLANK, true);
                    if (values == null || values.size() != 3) {
                        // format error
                        StringBuilder sb = buildResponse("request format error");
                        writer.write(sb.toString());
                        writer.flush();
                        close(socket);
                        break;
                    }
                    String method = values.get(0);
                    String url = values.get(1);
                    String protocol = values.get(2);

                    if (!"GET".equalsIgnoreCase(method)) {
                        // only support GET
                        StringBuilder sb = buildResponse("only GET support");
                        writer.write(sb.toString());
                        writer.flush();
                        close(socket);
                        break;
                    }
                    Map<String, String> head = buildHead(lines);
                    HttpRequest request = new HttpRequest(method, url, protocol, head);

                    //
                    // String body = count+","+LocalDateTime.now()+","+socket+","+request;
                    String body = buildResult(request);
                    //
                    StringBuilder sb = buildResponse(body);

                    writer.write(sb.toString());
                    writer.flush();
                } catch (Throwable e) {
                    System.out.println("socket error,socket=" + socket + "," + e);
                    close(socket);
                    break;
                }
            }
            // end while
        }
        // end run

        public void shutdown() {
            if (socket == null || socket.isClosed()) {
                return;
            }
            close(socket);
        }
    }
    // end WorkThread

    private static final void close(Socket socket) {
        if (socket == null) {
            return;
        }
        if (socket.isClosed()) {
            return;
        }
        try {
            socket.close();
        } catch (Throwable e) {
            System.out.println("socket close error," + socket + "," + e);
        }
    }

    private static final void close(ServerSocket socket) {
        if (socket == null) {
            return;
        }
        if (socket.isClosed()) {
            return;
        }
        try {
            socket.close();
        } catch (Throwable e) {
            System.out.println("server socket close error," + socket + "," + e);
        }
    }

    public static List<String> split(String str, String sep) {
        return split(str, sep, false);
    }

    public static List<String> split(String str, String sep, boolean ignoreEmpty) {
        if (str == null) {
            return null;
        }
        if (sep == null || EMPTY.equals(sep)) {
            sep = SEP;
        }

        List<String> list = new ArrayList<String>();
        int fromIndex = 0;

        int sepLen = sep.length();
        int pos = 0;
        String tmp = null;
        // a,b,c
        while (true) {
            pos = str.indexOf(sep, fromIndex);
            if (pos < 0) {
                list.add(str.substring(fromIndex));
                break;
            }
            tmp = str.substring(fromIndex, pos);
            if (ignoreEmpty && tmp.length() <= 0) {
                // ignore
            } else {
                list.add(tmp);
            }
            fromIndex = fromIndex + tmp.length() + sepLen;
        }
        return list;
    }

    private static final Map<String, String> buildHead(List<String> list) {
        int num = list.size();
        if (num <= 1) {
            return null;
        }
        Map<String, String> map = new LinkedHashMap<>();
        for (int i = 1; i < num; i++) {
            String line = list.get(i);
            int pos = line.indexOf(COLON);
            if (pos <= 0) {
                continue;
            }
            int len = line.length();
            if (pos >= len) {
                continue;
            }

            String key = line.substring(0, pos);
            String value = line.substring(pos + 1);
            key = key.trim();
            value = value.trim();

            if (key.isEmpty() || value.isEmpty()) {
                continue;
            }
            map.put(key, value);

        }
        return map;
    }

    public static final class HttpRequest {
        // GET /hello HTTP/1.1
        // Host: 127.0.0.1:8070
        public final String method;
        public final String url;
        public final String protocol;

        public final Map<String, String> head;

        public HttpRequest(String method, String url, String protocol, Map<String, String> head) {
            this.method = method;
            this.url = url;
            this.protocol = protocol;
            this.head = head;
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder(512);
            sb.append(method).append(",");
            sb.append(url).append(",");
            sb.append(protocol).append(",");
            sb.append(head);
            return sb.toString();
        }

    }

    /////
    private static String buildResult(HttpRequest request) {
        StringBuilder sb = new StringBuilder(1024 * 8);

        sb.append("<pre>");
        sb.append("start=" + GCTestMain.startTime + "\n");
        sb.append("now  =" + LocalDateTime.now() + "\n");
        sb.append("tps=" + GCTestMain.tps() + "\n");
        sb.append("heapUsed =").append(JvmUtil.heapUsed()).append("\n");
        sb.append("heapUsed(MB) =").append(JvmUtil.heapUsedMb()).append("\n");
        sb.append("heapUsed(GB) =").append(JvmUtil.heapUsedGb()).append("\n");

        Map<String, String> status = JvmUtil.getStatus();
        Set<String> keys = status.keySet();
        for (String key : keys) {
            sb.append(key).append("=").append(status.get(key)).append("\n");
        }
        sb.append("lastClearRunTime=" + GCTestMain.lastClearRunTime + "\n");
        sb.append("lastClearRunTimeAt=" + GCTestMain.lastClearRunTimeAt + "\n");
        sb.append("clearRunCount=" + GCTestMain.clearRunCount + "\n");
        sb.append("clearRunTotalTime=" + GCTestMain.clearRunTotalTime + "\n");
        sb.append("clearCountTotal=" + GCTestMain.clearCountTotal + "\n");
        sb.append("lastClearInfo=" + GCTestMain.lastClearInfo + "\n");
        // long runStartTime = GCTestMain.runStartTime;
        long totalKeyCount = GCTestMain.totalKeyCount.get();
        // long nowtime = System.currentTimeMillis();
        // long tps = totalKeyCount / (nowtime - runStartTime);
        sb.append("totalKeyCount=" + totalKeyCount + "\n");
        // sb.append("keyCountOpTps=" + tps + "\n");

        sb.append("datamap.size=" + GCTestMain.datamap.size() + "\n");
        sb.append("errorCount=" + GCTestMain.errorCount + "\n");
        sb.append("lastError=" + GCTestMain.lastError + "\n");

        Map<String, String> params = CommUtil.parseHttpRequestParams(request.url);

        int bucketWidth = CommUtil.getInt(params, "bucketWidth");
        int maxCount = CommUtil.getInt(params, "maxCount");

        if (bucketWidth <= 0) {
            bucketWidth = 5;
        }
        if (maxCount <= 0) {
            maxCount = 20;
        }

        sb.append(JvmPauseMonitor.getCountInfo(bucketWidth, maxCount));
        sb.append("\n");

        sb.append("</pre>");

        return sb.toString();
    }

}
